package com.yhb.channel.impl.Scalable.http;

import com.yhb.channel.impl.Scalable.ScalableChannel;
import com.yhb.channel.impl.Scalable.ScalableConstant;
import com.yhb.channel.impl.Scalable.ScalableException;
import com.yhb.channel.provider.ChannelBaseService;
import com.yhb.channel.utils.ChannelJSONUtil;
import com.yhb.channel.utils.ChannelLogger;
import lombok.var;
import okhttp3.*;
import org.apache.commons.codec.binary.Hex;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class ScalableHttpClient {

    /* ------------配置------------ */
    @Value("${scalableChannel.url}")
    private String baseUrl;

    @Autowired
    private ChannelBaseService channelBaseService;

    private static ChannelLogger logger;
    /**
     * 超时时间太短 10s->60s
     */
    private static final OkHttpClient okHttpClient =
            new OkHttpClient.Builder().callTimeout(60, TimeUnit.SECONDS).build();
    public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");

    @Value("${crypto.channel.hmacSha256.secret}")
    private String secret;

    @PostConstruct
    private void init() {
        logger = new ChannelLogger(ScalableChannel.CHANNEL_BASE_NAME,
                ScalableException.class,
                channelBaseService.iUtilProvider);
    }

    @NotNull
    public static String getParamSignString(Map<String, Object> param) {
        TreeMap<String, Object> sortedMap = new TreeMap<>(param);
        StringBuilder sb = new StringBuilder();

        for (Map.Entry<String, Object> entry : sortedMap.entrySet()) {
            sb.append(entry.getKey()).append("=").append(Optional.ofNullable(entry.getValue()).orElse("null").toString()).append("&");
        }

        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }

    @NotNull
    private String calHmac(String input) {
        SecretKeySpec spec;
        try {
            var m = Mac.getInstance("HmacSHA256");
            spec = new SecretKeySpec(Base64.getDecoder().decode(secret), "HmacSHA256");
            m.init(spec);
            return Hex.encodeHexString(m.doFinal(input.getBytes())).toUpperCase();
        } catch (Throwable t) {
            throw new RuntimeException(t);
        }
    }

    public HashMap<String, Object> http(String methodName, HashMap<String, Object> requestBody, String channelFullType) {
        final var nonceStr = UUID.randomUUID().toString().replace("-", "");
        requestBody.put("nonceStr", nonceStr);
        logger.info("第三方通道nonceStr: " + nonceStr);
        var paramsString = getParamSignString(requestBody); // 拼接参数
        var sign = calHmac(paramsString); // 计算签名
        logger.info("第三方通道签名结果: " + sign);
        var requestId = (String) requestBody.get("requestId");

        final String jsonData = ChannelJSONUtil.toJson(requestBody);
        var url = baseUrl + channelFullType + "?method=" + methodName;
        if (channelFullType.contains("SHANGMENG-DHL")) {
            // 商盟通道是旧的API fixme
            url = "https://channel.yinhebao.cn/api/v1/"  + channelFullType + "?method=" + methodName;
        }
        logger.info("开始请求: " + url);
        logger.logRequest(methodName, requestId, jsonData);

        try {
            var resStr = doPost(url,sign,nonceStr,jsonData);
            /*var response = okHttpClient.newCall(
                    new Request.Builder()
                        .header("sign", sign)
                        .header("nonceStr", nonceStr)
                        .url(url)
                        .post(RequestBody.create(jsonData, JSON))
                        .build()
            ).execute();

            // 检查请求是否成功
            if (!response.isSuccessful()) {
                throw new ScalableException(ScalableConstant.NETWORK_ERROR,
                        methodName + "请求\n" +
                                "{ " + requestId + " }\n" +
                                "发生{ " + response.code() + " }异常: \n"
                                + response.message());
            }*/
            // 取出json
            /*var resStr = "";
            ResponseBody resBody = response.body();
            if (resBody != null) {
                resStr = resBody.string();
            }*/
            // 返回
            logger.logResponse(methodName, requestId, resStr);
            var res = ChannelJSONUtil.parseJson(resStr, ScalableResponse.class);
            if (!res.isSuccess()) {
                throw new ScalableException(ScalableConstant.INNER_ERROR,
                        methodName +  "响应\n" +
                        "{ " + requestId + " }\n" +
                        "返回通道{ " + res.getCode() + " }错误: \n" +
                        res.getMessage()
                );
            }
            return res.getData();
        } catch (IOException e) {
            throw new ScalableException(ScalableConstant.ERROR_DEFAULT_CODE,
                    methodName +  "请求" + requestId + "内部错误: " + e.getMessage(), e);
        } catch (Exception e) {
            throw new ScalableException(ScalableConstant.ERROR_DEFAULT_CODE,
                    methodName +  "请求" + requestId + "内部错误: " + e.getMessage(), e);
        }
    }

    public static String doPost(String url,String sign,String nonceStr, String content)throws Exception {
        /**
         * 发送请求
         */
        logger.info("postUrl :{} postContent:{}", url, content);
        OutputStream outputStream = null;
        OutputStreamWriter outputStreamWriter = null;
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader reader = null;
        StringBuffer resultBuffer = new StringBuffer();
        String tempLine = null;
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection connection = realUrl.openConnection();
            HttpURLConnection httpURLConnection = (HttpURLConnection) connection;

            httpURLConnection.setDoOutput(true);
            httpURLConnection.setRequestMethod("POST");
            httpURLConnection.setRequestProperty("Accept-Charset", "utf-8");
            httpURLConnection.setRequestProperty("Content-Type", "application/json");
            httpURLConnection.setRequestProperty("Content-Length", String.valueOf(content.length()));
            httpURLConnection.setRequestProperty("sign", sign);
            httpURLConnection.setRequestProperty("nonceStr", nonceStr);
            /**
             * 链接超时
             * 读取超时
             */
            httpURLConnection.setConnectTimeout(60 * 1000);
            httpURLConnection.setReadTimeout(60 * 1000);

            outputStream = httpURLConnection.getOutputStream();
            outputStreamWriter = new OutputStreamWriter(outputStream);
            outputStreamWriter.write(content);
            outputStreamWriter.flush();

            inputStream = httpURLConnection.getInputStream();
            inputStreamReader = new InputStreamReader(inputStream);
            reader = new BufferedReader(inputStreamReader);
            while ((tempLine = reader.readLine()) != null) {
                resultBuffer.append(tempLine);
            }
            return resultBuffer.toString();
        } catch (Exception ex) {
            throw ex;
        } finally {
            try {
                if (outputStreamWriter != null) {
                    outputStreamWriter.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
                if (reader != null) {
                    reader.close();
                }
                if (inputStreamReader != null) {
                    inputStreamReader.close();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (Exception ex) {
                throw ex;
            }
        }
    }
}
